﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Text.RegularExpressions;

/* Universität Trier
 * Fachbereich II - Linguistische Datenverarbeitung
 * Bachelor of Arts
 * Computerlinguistik (HF), English Language and Linguistics (NF)
 * 
 * 
 * A Heuristic Approach to Anaphora Resolution
 * 
 * Bachelor-Arbeit
 * 15. November 2013
 * 
 * Name:			Christopher Michels
 * Matrikelnummer:	1007830
 * 
 * Adresse:			Universitätsring 8d
 * 
 * Telefon:			(06 51) 99 24 11 55
 * E-Mail:			s2chmich@uni-trier.de
 * 
 * Erstprüfer: 		Dr. Sven Naumann
 * Zweitprüfer:		Prof. Dr. Reinhard Köhler
 * 
 * Sources:
 * Biber, Douglas et al. (1999). Longman Grammar of Spoken and Wri􀆩en English. Studies in Language and Linguistics. Harlow, UK: Longman.
 * Mitkov, Ruslan (1998). “Robust pronoun resolution with limited knowledge”. In: Proceedings of the 36th Annual Meeting of the Association for Computational Linguistics and 17th International Conference on Computational Linguistics. Vol. 2. ACL '98. Montreal, Canada: Association for Computational Linguistics, 869–875.
 * Mitkov, Ruslan (2000). “Pronoun Resolution: The Practical Alternative”. In: Corpus-based and Computational Approaches to Discourse Anaphora. Ed. by Simon Botley and Anthony Mark McEnery. Studies in Corpus Linguistics. J. Benjamins Pub. Co.
 * Mitkov, Ruslan (2002). Anaphora resolution. Studies in Language and Linguistics. London, UK: Longman.
 * Santorini, Beatrice (1991). Part-of-Speech Tagging Guidelines for the Penn Treebank Project. URL: http://www.cis.uni-muenchen.de/~schmid/tools/TreeTagger/data/Penn-Treebank-Tagset.pdf (visited on 11/06/2013).
 * Schmid, Helmut (1994). “Probabilistic Part-of-Speech Tagging Using Decision Trees”. In: Proceedings of the International Conference on New Methods in Language Processing. Manchester, UK.
 * Schmid, Helmut (1995). “Improvements In Part-of-Speech Tagging With an Application To German”. In: Proceedings of the ACL SIGDAT-Workshop. Dublin, Ireland.
 * Textalyser (2004). Bernhard Huber Internet Engineering Company. URL: http://textalyser.net (visited on 10/23/2013).
 */

namespace KPAR
{

    class Candidate : Anaphora
    {
        /* Takes a list of pronouns to ignore as candidates, the number
         * and the gender of the pronoun, and the relevant data for all 
         * potential candidates. It filters these potential candidates
         * with the help of these date and returns a list of appropriate
         * candidates.
         */
        public static List<Candidate> GetCandidates(List<string> pronounIDs, 
                                        string pNumber, string pGender, 
                                        XElement allCandidatesData)
        {
            List<Candidate> candidates = new List<Candidate>();

            //Unpack the potential candidates from allCandidatesData.
            IEnumerable<XElement> sentences =
                from s in allCandidatesData.Descendants("sentence")
                where s.Parent.Name == "sentences"
                select s;

            IEnumerable<XElement> nouns =
                from n in allCandidatesData.Descendants("NC")
                where n.Parent.Name == "nouns"
                select n;

            //Check the potential candidates for number and gender.
            //List the verified ones as candidate nouns. 
            List<XElement> candidateNouns = new List<XElement>();

            foreach (XElement noun in nouns)
            {
                if (!pronounIDs.Contains(noun.SplitID().Last()))
                {
                    if (CheckNumberAndGender(
                            pNumber, pGender,
                            noun.Attribute("number").Value,
                            noun.Attribute("gender").Value))
                    {
                        candidateNouns.Add(noun);
                    }
                }
            }

            //If the gender and number agreement cannot verify any candidate, leave the
            //decision to the indicators only.
            if (candidateNouns.Count == 0)
            {
                candidateNouns = nouns.ToList();
            }

            foreach (XElement candidate in candidateNouns)
            {
                //Assemble necessary data for verified candidates.
                XElement cData = new XElement("CandidateData");

                XElement sentence =
                    (from s in sentences
                     where candidate.SplitID()[2].Equals(s.SplitID()[2])
                     select s).First();

                cData.Add(new XElement("candidate", candidate));
                cData.Add(sentence);

                //Create the verified candidates and list them.
                candidates.Add(new Candidate(cData));
            }

            return candidates;
        }

        /* Takes number and gender of both the pronoun and the 
         * candidate, checking these values for agreement.
         * Returns true if agreement holds in both cases, false if not.
         */
        private static bool CheckNumberAndGender(string pNumber, string pGender,
                                                 string nNumber, string nGender)
        {
            bool numberAgr = false;
            bool genderAgr = false;

            if (nGender.Contains(":"))
            {
                nGender = nGender.Split(new char[] { ':' })[0];
                nNumber = nNumber.Split(new char[] { ':' })[0];
            }

            //Do pronoun and candidate agree in number?
            if (pNumber.Equals("indisc"))
            {
                numberAgr = true;
            }
            else
            {
                if (nNumber.Equals(pNumber)
                    || nNumber.Equals("indisc"))
                {
                    numberAgr = true;
                }
            }

            //Do pronoun and candidate agree in gender?
            if (pGender.Equals("indisc"))
            {
                genderAgr = true;
            }
            else
            {
                if (nGender.Equals(pGender)
                    || nGender.Equals("indisc"))
                {
                    genderAgr = true;
                }
            }

            //If both questions can be answered with yes,
            //we have a verified candidate.
            return numberAgr && genderAgr;
        }

        /* Takes the list of candidates for a pronoun and tries to find a single
         * highscore candidate.
         */
        public static string SelectBestCandidate(List<Candidate> candidates)
        {
            //Sort the candidate list in descending order, according
            //to their score.
            candidates.Sort((Candidate C1, Candidate C2)
                => (C2.Score.CompareTo(C1.Score)));

            if (candidates.Count == 0)
            {
                return null;
            }
            else
            {
                //Determine the maximum score among them.
                int maxScore = candidates.Max((Candidate C) => C.Score);

                //Find all candidates with that maximum score.
                List<Candidate> highscoreCandidates =
                    candidates.TakeWhile((Candidate C)
                        => C.Score == maxScore).ToList();

                //If there is only one, select that one. 
                //If there are more than one, disambiguate
                //and return the selected candidate.
                Candidate selectedCandidate =
                    (highscoreCandidates.Count == 1) ?
                        highscoreCandidates.First() :
                            Disambiguate(highscoreCandidates);

                string selectedCandidateID = (selectedCandidate != null) ? selectedCandidate.ID.Last() : null;

                return selectedCandidateID;
            }
        }

        /* Baseline version of SelectBestCandidate
         */
        public static string SelectBestCandidateBase(List<Candidate> candidates)
        {
            //Compute the minimum value for Distance and return the 
            //candidate with that value.
            int MinDistance = candidates.Min(
                                (Candidate C2) => C2.Distance);
            Candidate selectedCandidate = candidates.Single(
                    (Candidate C1) => C1.Distance == MinDistance);

                string selectedCandidateID = (selectedCandidate != null) ? selectedCandidate.ID.Last() : null;

                return selectedCandidateID;
        }

        /* If there is more than one highscore candidate, this method takes that
         * list of candidates and determines the best candidate among them.
         */
        private static Candidate Disambiguate(List<Candidate> ambiguousCandidates)
        {
            //Look at specific important indicators in a specific order.
            foreach (int i in new int[] { 10, 7, 3 })
            {
                //Retrieve the maximum value for the current indicator.
                int MaxIndicator = ambiguousCandidates.Max(
                                    (Candidate C) => C.Indicators[i]);

                //Is there only one candidate with that maximum value?
                //If so, return that candidate.
                try
                {
                    return ambiguousCandidates.Single(
                            (Candidate C) => C.Indicators[i] == MaxIndicator);
                }
                //Otherwise, remove all candidates with lower values for that indicator
                //and move on to the next important indicator.
                catch
                {
                    try
                    {
                        ambiguousCandidates.RemoveAll(
                            (Candidate C) => C.Indicators[i] < MaxIndicator);
                    }
                    catch { }
                }
            }

            //If you reach this point, disambiguation has not been successful 
            //yet. Then compute the minimum value for Distance and return the 
            //candidate with that value.
            int MinDistance = ambiguousCandidates.Min(
                                (Candidate C2) => C2.Distance);
            return ambiguousCandidates.Single(
                    (Candidate C1) => C1.Distance == MinDistance);
        }

        #region CandidateFields

        public string[] ID { get; private set; }
        public string Number { get; private set; }
        public string Gender { get; private set; }
        public string[] SentID { get; private set; }

        public bool Definite { get; private set; }
        public bool Given { get; private set; }
        public bool Prepositional { get; private set; }
        public string NounPartsString { get; private set; }
        public string HeadLemma { get; private set; }
        public string[] CollocData { get; private set; }

        public int Distance { get; private set; }
        public int[] Indicators { get; private set; }
        public int Score { get; private set; }

        #endregion

        /* Default/void Constructor
         */
        public Candidate() { }

        /* Main Constructor who gets relevant candidate data,
         * assigning values to fields required for determining
         * the indicators for a specific candidate.
         */
        public Candidate(XElement candidateData)
        {
            //Unpack data from candidateData.
            XElement candidate =
                (from c in candidateData.Descendants("NC")
                 where c.Parent.Name == "candidate"
                 select c).First();

            XElement sentence =
                (from s in candidateData.Descendants("sentence")
                 select s).First();

            //Save basic information.
            ID = candidate.SplitID();
            if (candidate.Attribute("gender").Value.Contains(":"))
            {
                Number = candidate.Attribute("number").Value.Split(new char[] { ':' })[0];
                Gender = candidate.Attribute("gender").Value.Split(new char[] { ':' })[0];
            }
            else
            {
                Number = candidate.Attribute("number").Value;
                Gender = candidate.Attribute("gender").Value;
            }
            SentID = sentence.SplitID();

            //Pass unpacked data on for the purpose of evaluation.
            EvaluateData(candidate, sentence);
        }

        /* Gets the XElement of the candidate and of the sentence
         * it occurs in and evaluates the information in these elements
         * for the purpose of determining the candidate indicators.
         */
        private void EvaluateData(XElement candidate, XElement sentence)
        {
            #region DefinitenessAndNonCandidatesData

            Definite = false;

            //According to the lists of definite and indefinite articles,
            //modify the fields Definite and Indefinite for the current
            //candidate.
            if (candidate.Descendants().Count() > 1)
            {
                foreach (XElement nounElem in candidate.Descendants())
                {
                    if (DefArticles.Contains(nounElem.Value.ToLower()))
                        Definite = true;
                    else if (IndefArticles.Contains(nounElem.Value.ToLower()))
                        Definite = false;
                }
            }

            #endregion

            #region GivennessData

            Given = ID.Last().Equals(sentence.Descendants("NC").First().SplitID().Last());

            #endregion

            #region Prepositional

            Prepositional = (candidate.Attribute("prepositional").Value.Equals("True")); 

            #endregion

            #region LexicalData

            //Find the noun token strings.
            IEnumerable<string> cNounParts =
                from token in candidate.Descendants("token")
                where token.Attribute("pos").Value.StartsWith("N")
                select token.Value;

            //Concatenate them to create a single string.
            NounPartsString = String.Join(" ", cNounParts).Trim();
            
            //Find the last noun token of the current NC, if possible.
            IEnumerable<string> cLemmas =
                from token in candidate.Descendants("token")
                where token.Attribute("pos").Value.StartsWith("N")
                select token.Attribute("lemma").Value;

            //Get the lemma of that token.
            HeadLemma = cLemmas.LastOrDefault();

            #endregion

            #region CollocationPreferenceCandidateData

            //Find the candidate unisolated/in its context.
            XElement c =
                (from n in sentence.Descendants("NC")
                 where n.Attribute("id").Value == candidate.Attribute("id").Value
                 select n).First();

            //Find the neighbours of the candidate, if possible.
            XElement precedingElemCand = c.ElementsBeforeSelf().LastOrDefault();
            XElement nextElemCand = c.ElementsAfterSelf().FirstOrDefault();

            //Are these neighbours VCs?
            XElement precedingVC_Cand =
                (precedingElemCand != null && precedingElemCand.Name == "VC") ?
                    precedingElemCand : null;
            XElement nextVC_Cand =
                (nextElemCand != null && nextElemCand.Name == "VC") ?
                    nextElemCand : null;

            //VC lemmata go here as results.
            string collocVC_PreCand = null;
            string collocVC_PostCand = null;

            //Find the results based on preceding/following VCs.
            if (precedingVC_Cand != null)
            {
                //Find the last token of the verb, if possible.
                IEnumerable<XElement> tokensOfVC_PreCand =
                    from token in precedingVC_Cand.Descendants("token")
                    where token.Attribute("pos").Value.StartsWith("V")
                    select token;
                XElement lastTokenOfVC_PreCand =
                    tokensOfVC_PreCand.LastOrDefault();

                //Get the lemma of that token.
                if (lastTokenOfVC_PreCand != null)
                    collocVC_PreCand =
                        lastTokenOfVC_PreCand.Attribute("lemma").Value;
            }
            if (nextVC_Cand != null)
            {
                //Find the last token of the verb, if possible.
                XElement lastTokenOfVC_PostCand =
                    (from token in nextVC_Cand.Descendants("token")
                     where token.Attribute("pos").Value.StartsWith("V")
                     select token).LastOrDefault();
                //Get the lemma of that token or, in case of "be", try to get
                //the following adjective (complex).
                if (lastTokenOfVC_PostCand != null)
                {
                    if (lastTokenOfVC_PostCand.Attribute("lemma").Value.Equals("be"))
                    {
                        XElement followingADJC = lastTokenOfVC_PostCand.Parent.ElementsAfterSelf().FirstOrDefault();
                        if (followingADJC != null
                            && followingADJC.Name == "ADJC")
                        {
                            IEnumerable<string> stringsOfADJC =
                                from token in followingADJC.Descendants("token")
                                select token.Value;

                            collocVC_PostCand = String.Join(" ", stringsOfADJC).Trim();
                        }
                        else
                        {
                            collocVC_PostCand =
                                lastTokenOfVC_PostCand.Attribute("lemma").Value;
                        }
                    }
                    else
                    {
                        collocVC_PostCand =
                            lastTokenOfVC_PostCand.Attribute("lemma").Value;
                    }
                }
            }

            //Save results for this candidate in CollocData.
            CollocData = new string[] { collocVC_PreCand, collocVC_PostCand };

            #endregion
        }

        /* Takes the current pronoun to access relevant pronoun data.
         * Determines the candidate's indicators based on both the
         * pronoun's data and its own data.
         */
        public void DetermineIndicators(Pronoun p)
        {
            //Compute distance via partial IDs.
            Distance = Int32.Parse(p.ID.Last()) - Int32.Parse(ID.Last());

            //Create and initialize an integer array for the indicators
            //of the current candidate.
            int[] indicators = new int[12];

            for (int i = 0; i < indicators.Length; i++)
                indicators[i] = -9;

            //#00 Definiteness & #12 Non-Candidates
            #region DefinitenessAndNonCandidates

            //If there are any determiners in the relevant paragraphs,
            //use this indicator. Always assign the score 0 otherwise.
            if (!p.IgnoreDefiniteness)
            {
                if (Definite)
                    indicators[0] = 0;
                else
                    indicators[0] = -1;
            }
            else
            {
                indicators[0] = 0;
            }

            #endregion

            //#01 Givenness
            #region Givenness

            if (Given)
                indicators[1] = 1;
            else
                indicators[1] = 0;

            #endregion

            //#02 Domain Concept Preference
            #region DomainConceptPreference

            bool matchesDomainConcepts = false;

            //Check whether the candidate represents a domain concept.
            if (HeadLemma != null)
            {
                foreach (string domainConcept in DomainConcepts)
                {
                    if ((domainConcept.ToLower().Contains(HeadLemma.ToLower())) ||
                        (domainConcept.ToLower().Contains(NounPartsString.ToLower())))
                    {
                        matchesDomainConcepts = true;
                        break;
                    }
                }
            }

            if (matchesDomainConcepts)
                indicators[2] = 1;
            else
                indicators[2] = 0;

            #endregion

            //#03 Verb Preference
            #region VerbPreference

            if (p.VCSelectedNPs.Contains(ID.Last()))
                indicators[3] = 1;
            else
                indicators[3] = 0;

            #endregion

            //#04 Noun Phrase Preference
            #region NounPhrasePreference

            if (p.NCSelectedNPs.Contains(ID.Last()))
                indicators[4] = 1;
            else
                indicators[4] = 0;

            #endregion

            //#05 Lexical Reiteration
            #region LexicalReiteration

            //Count how many times the candidate occurs within the
            //relevant paragraphs.
            int reiterationCount = 0;

            if (HeadLemma != null)
            {
                foreach (string stringOfParNC in p.StringsOfParNCsIE)
                {
                    if ((stringOfParNC.ToLower().Contains(HeadLemma.ToLower())) ||
                        (stringOfParNC.ToLower().Contains(NounPartsString.ToLower())))
                    {
                        reiterationCount++;
                    }
                }
            }

            if ((reiterationCount - 1) >= 2)
                indicators[5] = 2;
            else if ((reiterationCount - 1) == 1)
                indicators[5] = 1;
            else
                indicators[5] = 0;

            #endregion

            //#06 Section Heading Preference
            #region SectionHeadingPreference

            bool containedInHeadNCs = false;

            //Does the candidate occur in the relevant heading?
            if (HeadLemma != null)
            {
                foreach (string stringOfHeadNC in p.StringsOfHeadNCsIE)
                {
                    if ((stringOfHeadNC.ToLower().Contains(HeadLemma.ToLower())) ||
                        (stringOfHeadNC.ToLower().Contains(NounPartsString.ToLower())))
                    {
                        containedInHeadNCs = true;
                        break;
                    }
                }
            }

            if (containedInHeadNCs)
                indicators[6] = 1;
            else
                indicators[6] = 0;

            #endregion

            //#07 Collocation Pattern Preference
            #region CollocationPatternPreference

            bool successfulCollocPref = false;

            if (p.CollocData[0] != null 
                && CollocData[0] != null)
            {
                if (p.CollocData[0].Equals(CollocData[0])
                    || (p.CollocData[0]).Equals("un" + CollocData[0])
                    || ("un" + p.CollocData[0]).Equals(CollocData[0]))
                    successfulCollocPref = true;
            }
            else if (p.CollocData[1] != null
                     && CollocData[1] != null)
            {
                if (p.CollocData[1].Equals(CollocData[1])
                    || (p.CollocData[1]).Equals("un" + CollocData[1])
                    || ("un" + p.CollocData[1]).Equals(CollocData[1]))
                    successfulCollocPref = true;
            } 

            if (successfulCollocPref)
                indicators[7] = 2;
            else
                indicators[7] = 0;

            #endregion

            //Store some partial IDs as integer values for 
            //access of integer comparison.
            int pSentID = Int32.Parse(p.ID[2]);
            int cSentID = Int32.Parse(SentID.Last());
            int cID = Int32.Parse(ID.Last());

            //#08 Referential Distance
            #region ReferentialDistance

            if (pSentID == cSentID)
            {
                //If the candidate occurs in the preceding clause
                //of the pronoun, score 2 instead of just 1 for 
                //occurring in the same sentence.
                if (p.PrecClauseLimits[1] > 0)
                {
                    if (p.PrecClauseLimits[0] < cID && cID < p.PrecClauseLimits[1])
                        indicators[8] = 2;
                    else
                        indicators[8] = 1;
                }
                else
                {
                    indicators[8] = 1;
                }
            }
            else if (cSentID == (pSentID - 1))
                indicators[8] = 0;
            else if (cSentID == (pSentID - 2))
            {
                indicators[8] = -1;
            }

            #endregion

            //#09 "Non-prepositional" Noun Phrases
            #region NonPrepositionalNounPhrases

            if (Prepositional)
                indicators[9] = -1;
            else
                indicators[9] = 0;

            #endregion

            //#10 "Immediate Reference"
            #region ImmediateReference

            if (p.NounIDforIR > 0 &&
                (cSentID == pSentID) &&
                (p.NounIDforIR == cID))
            {
                indicators[10] = 2;
            }
            else
            {
                indicators[10] = 0;
            }

            #endregion

            //#11 Antecedent-pointing Constructions
            #region AntecedentPointingConstructions

            if (p.FocusedNPs.Contains(ID.Last()))
                indicators[11] = 2;
            else
                indicators[11] = 0;

            #endregion

            //Save the indicators array and save the sum of the scores.
            Indicators = indicators;
            Score = Indicators.Sum();
        }

        /* Returns a string representation of the current candidate.
         * Overrides the ToString() method of the class Object.
         */
        public override string ToString()
        {
            string output = "";
            output =
                output + "Candidate:\t" + "| Score: " + Indicators.Sum() + " |\t| Distance: " + Distance + " |\t"
                + "\t" + Indicators[0] + "\t" + Indicators[1] + "\t" + Indicators[2] + "\t" + Indicators[3] + "\t" + Indicators[4]
                + "\t" + Indicators[5] + "\t" + Indicators[6] + "\t" + Indicators[7] + "\t" + Indicators[8] + "\t" + Indicators[9] 
                + "\t" + Indicators[10] + "\t" + Indicators[11]
                + " | " + String.Join(":", ID) + " |\t| " + Number + ":" + Gender 
                + " |" + "\tGIV:" + Given + ", DEF:" + Definite + ", COL:" + String.Join(":", CollocData) + ", " + NounPartsString + ":" + HeadLemma;
            return output;
        }
    }
}
